1665年春天,伦敦爆发了鼠疫。据说那个时候剑桥关了,牛顿只能离开学校回到他的老家伍尔索普庄园。在老家的十八个月里,没有人干扰,他做了许多著名的实验,甚至是在那里发现了万有引力定律。这个故事告诉我们,躲到一个没人的地方专心工作,可能会有大收获。
因为Covid-19疫情的关系,武汉封城已经超过3个星期了,而我也已经被困在西雅图快2周了。回程的航班几乎都取消了,就算回去了估计还得继续自我隔离14天,也没办法在北京呆着。既来之则安之,反正2016年我也在美国工作了一整年,远程工作也是的得心应手,其实也没有什么事情是非得见面谈的。全球Office 365在美国的访问速度比在数码科技广场快得不是一点点,感觉效率突飞猛进,一天可以工作将近14-16个小时,还能省下很多开会的时间,心情觉得特别愉快。上个月在巴厘岛开会的时候,提到了要做知识图谱。这个概念我听过很多遍,不过具体工作上只是2004年在IBM中国研究院做过一点点本体(Ontology)和BPML的最基本的研究,十五年来对这方面的技术基本没有做跟进,只能是个门外汉,最多也就是会用XMind画个思维导图啥的。“学而不思则罔,思而不学则殆”,画完这个树状的思维导图其实我不太满意。因为这只是一个思维导图,而不是知识图谱。我又不想随便拍脑袋想个东西,最终没啥用,这种连“思而不学则殆”都算不上。要做就选择一条专业的路,虽然可能做不到100分,至少能做个60分,也算不辜负花掉的时间。“工欲善其事,必先利其器”,做一件事情之前,专业的工具选择是必须的。之前还要清晰地理解概念,而不是想当然。造概念这种事情,在商业上还情有可原,甚至可以是锦上添花;技术上,很有可能会万劫不复。我一开始,从定义和概念开始,分析什么是知识图谱(Knowledge Graph)。KG的各种各样的概念很多,在Wikipedia上被跳转到了Ontology(本体)这个概念上,或者是Google Knowledge Graph。我在Wiki的引用文章里找到了一篇Ontotext网站上2018年的定义,我觉得写得比较清晰:知识图表示实体(真实对象、事件、情况或抽象概念)的相互关联描述的集合,其中:
• 描述具有一个正式结构,允许人和计算机以高效和明确的方式处理它们;
• 实体描述相互促进,形成一个网络,其中每个实体表示与实体相关的实体描述的一部分。
“https://www.ontotext.com/knowledgehub/fundamentals/what-is-a-knowledge-graph/”
知识图谱的核心是图(Graph)。存储知识图谱的数据结构,很明显用图是最直观的。这种数据结构重点描述了各不同实体之间的关系,跟传统的实体(Entity)和关系(Relationship)的关系型数据库完全不同。为了能够把知识图谱用一个好的方式展现出来,需要一个新的数据库,这种数据库毋庸置疑,就是图数据库(Graph Database)。市场上现在有很多不同类型的图数据库,最流行的就是Neo4j,支持ACID事务,支持本地的图存储和处理。为了仔细研究下怎么做知识图谱,我花了一整天的时间研究了Neo4j的相关内容,包括数据库, Desktop以及查询语言Cypher,还写了一段简单的代码用C#进行数据库的访问。这样才能有第一手的感觉,了解这种数据库是不是真正像它说的那样,适合构建企业的知识图谱。研究过程特别顺手,所有的视频资料都在Youtube上,已经人肉翻墙,速度流畅。
下面我主要讲一下我用Neo4j Desktop建模和Cypher的一些直观感受,我假设大家都懂数据库技术,如果没有数据库基础或者对技术不感兴趣的可以直接跳到结论的部分。传统的关系型数据库主要用ER图的方式,重点在实体及属性的描述,对关系的支持主要靠外键(Foreign key),对性能的优化主要靠索引(Index)。如果数据关系比较多的话,恰好数据库设计又用了Boyce–Codd范式,那么跨表写SQL语句进行Join就会特别复杂,在查询的过程当中需要通过索引进行性能优化。由于传统关系型数据库的存储数据结构以是B+树为基础,所以跨表查询的成本是非常高的。这种数据结构用来描述传统的数据很成功,但是如果关系特别多,属性不多的话,麻烦就来了。以我以前的经验来看,对十几张表写个几百行的SQL语句进行查询稀松平常。Graph数据库就用了一种完全不一样的方式来存储数据,定义了节点,关系,属性等等。这种设计方式跟人类普通的思维方式也更加贴合一些。比如下图,就定义了汤姆汉克斯出演过哪些电影,导演过哪些电影,跟谁一起演了什么电影。
其实可以看到这个跟我们平时在白板上画的关系思路非常接近。我也很喜欢neo4j在数据建模的文档里写的“白板友好”,这个图就跟白板上画的东西讲得很像:
Cypher是Neo4j的查询语言,跟SQL完全不一样。他的设计目标是易于理解使用,同时还集成SQL的语言的功能。我觉得这种设计特别有趣的地方是用了一个叫做ASCII艺术的东西,让我想起了以前在BBS上通过ASCII码画画的历史。
比如这一句,就是查询电影云图的导演,这里最搞笑的是用了->这种符号来描述“有向"的"关系",虽然看上去跟SQL那种join来join去的样子格格不入,不过的确是简单明了。我还记得以前我有个同学在写c语言的时候指针用了<-而不是->,然后还问我为啥编译错误。我当时嘲笑他说你以为你是画箭头呢,方向没区别,没想到在这个地方被Cypher的设计者发扬光大了。又比如下一句,就是找到跟汤姆汉克斯还没有合作过的影星的距离。在这个示例模型里,他跟Zach Grenier的距离是5。这种功能如果用传统的SQL语句,估计要写疯掉。Cypher的功能很强大,有很多内置的聚合函数,如果不够还能够通过APOC进行自定义开发和扩展,类似于SQL中的自定义存储过程,源代码都在github上开源了:https://github.com/neo4j-contrib/neo4j-apoc-procedures
由于我写的时间还比较短,还没记住他所有的关键字,好在他搞了一个速查表,各种关键字和解释都在里面,以后写的时候可以速查:https://neo4j.com/docs/cypher-refcard/
我在学习neo4j的时候主要用了两个环境,一个是默认提供的云上的沙盒,另外一个是本地环境。比较而言还是本地的性能速度更快一些,当然云上的也不错,其实都是一个东西。Neo4j Desktop是桌面的管理工具,支持创建项目,创建数据库实例,添加图数据库,应用和插件等等,我刚才提到的APOC就是一个插件。可以通过Neo4j浏览器直接打开帮助,用Cypher进行查询,支持智能感知,查询结果可以实时交互,图的节点也可以编辑和拖动,非常方便。好像企查查在网页上查询公司之间,股东之间关系的UI跟这个很像。作为码农,我还安装了官方支持的.NET的Driver,用C#写了一段代码进行操作,可惜4.0版本是10天前,也就是2020年2月4号才发布的,网站上的示例代码是错的!这个更新里IDriver接口去掉了同步方法Session(),而改成了异步的AsyncSession(),导致我花了半个小时查原因改代码。public class HelloWorldExample : IDisposable
{
private readonly IDriver _driver;
public HelloWorldExample(string uri, string user, string password)
{
_driver = GraphDatabase.Driver(uri, AuthTokens.Basic(user, password));
}
public void PrintGreeting(string message)
{
using (var session = _driver.Session())
{
var greeting = session.WriteTransaction(tx =>
{
var result = tx.Run("CREATE (a:Greeting) " +
"SET a.message = $message " +
"RETURN a.message + ', from node ' + id(a)",
new {message});
return result.Single()[0].As<string>();
});
Console.WriteLine(greeting);
}
}
public void Dispose()
{
_driver?.Dispose();
}
public static void Main()
{
using (var greeter = new HelloWorldExample("bolt://localhost:7687", "neo4j", "password"))
{
greeter.PrintGreeting("hello, world");
}
}
}
public class HelloWorldExample : IDisposable
{
private readonly IDriver _driver;
public HelloWorldExample(string uri, string user, string password)
{
_driver = GraphDatabase.Driver(uri, AuthTokens.Basic(user, password));
}
public async void PrintGreeting(string message)
{
IAsyncSession session = _driver.AsyncSession(o => o.WithDatabase("neo4j"));
try
{
IResultCursor cursor = await session.RunAsync("CREATE (a:Greeting) " +
"SET a.message = $message " +
"RETURN a.message + ', from node ' + id(a)", new { message });
await cursor.ConsumeAsync();
}
finally
{
await session.CloseAsync();
}
await _driver.CloseAsync();
}
public void Dispose()
{
_driver?.Dispose();
}
public static void Main()
{
using (var greeter = new HelloWorldExample("bolt://localhost:7687", "neo4j", "password"))
{
greeter.PrintGreeting("hello, world");
}
}
}
经过短暂的对图数据库的研究,我觉得这真是一个非常适合管理和存储知识图谱的数据库,尤其是对于企业各种复杂的关系型的数据。当然所有的云厂商其实都有现成的图数据库产品提供,例如AWS的Neptune,Azure的Costmos和Oracle也能支持图数据库。对于使用者而言,关键在于设计思路和查询方式的不同。在Neo4j的网站上,讲到了图数据库可以用在人工智能,知识图谱,金融防欺诈,主数据管理,实时推荐,社交网络等等用例中。网站上还有一个GraphGists的版块(https://neo4j.com/graphgists/),专门提供各种示例的数据库,帮助实际应用进行建模,文档和资料也比较全,不愧为是最流行的图数据库。
我在GraphGists里我找到了一个AWS产品的图数据库,描述了各种AWS产品,所在大陆,区域,可用区,价格等等,数据不是很全也不是最新的,不过特别有示范意义。https://neo4j.com/graphgist/amazon-web-services-global-infrastructure-graph
我猜应该已经有很多公司的产品在用图数据库了,除了我刚才提到的企查查很明显的用了以外,我还咨询了我在金融行业资深的安全专家同学,她跟我说国内最大的支付渠道公司已经在用Neo4j进行反洗钱分析了,包括计算赌博行为,营销套利,信用卡套现啥的特别有用,恰好这个项目也是她领导的。
总体而言,图数据库今天已经不是一个小众的数据库了,相信这是一个非常有价值的构建知识图谱的工具,接下去的工作是利用代码和工具,把海量的数据慢慢组织成图数据库的结构,进一步支持业务的决策和发展。